//****************************************************************************
// Copyright (C) 2001,2002,2003,2004  PEAK System-Technik GmbH
//
// linux@peak-system.com
// www.peak-system.com
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
//****************************************************************************

//****************************************************************************
//
// pcan_fifo.c - manages the ringbuffers for read and write data
//
// $Log: pcan_fifo.c,v $
// Revision 1.17  2004/08/14 20:40:59  klaus
// comment clarification
//
// Revision 1.16  2004/04/11 22:03:29  klaus
// cosmetic changes
//
// Revision 1.15  2004/03/27 16:57:06  klaus
// modified for use with kernels <= 2.2.14
//
// Revision 1.14  2004/03/27 15:10:54  klaus
// prepared for use with gcc 3.x, modified for use with kernels < 2.2.4
//
// Revision 1.13  2003/07/24 18:13:25  klaus
// contribution by Juergen Beisert: removed special handling of  <asm/system.h> for kernels 2.2 only
//
// Revision 1.12  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.11  2003/03/02 10:58:07  klaus
// merged USB thread into main path
//
// Revision 1.10.2.7  2003/02/09 13:18:09  klaus
// modifications to support linux 2.2.19 kernels
//
// Revision 1.10.2.6  2003/02/09 13:18:09  klaus
// modifications to support linux 2.2.19 kernels
//
// Revision 1.10.2.5  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.10.2.4  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.10.2.3  2003/01/28 23:28:26  klaus
// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
//
// Revision 1.10.2.2  2003/01/08 22:21:48  klaus
// fixed version 1.3 problem, write has still a deadlock
//
//****************************************************************************

//****************************************************************************
// INCLUDES
#include <src/pcan_common.h> 
#include <linux/types.h>
#include <linux/errno.h>    // error codes
#include <asm/system.h>     // cli(), save_flags(), restore_flags()

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,2,15)
#include <asm/spinlock.h>
#else
#include <linux/spinlock.h>
#endif

#include <src/pcan_fifo.h> 

//****************************************************************************
// DEFINES

//****************************************************************************
// GLOBALS

//****************************************************************************
// LOCALS

//****************************************************************************
// CODE  
int pcan_fifo_reset(register FIFO_MANAGER *anchor)
{
  spin_lock_irqsave(&anchor->lock, anchor->flags);
	anchor->dwTotal       = 0; 
	anchor->nStored       = 0; 
	anchor->r = anchor->w = anchor->bufferBegin; // nothing to read
  anchor->bPutClaimed   = 0;
  anchor->bGetClaimed   = 0;
	spin_unlock_irqrestore(&anchor->lock, anchor->flags);

  // DPRINTK(KERN_DEBUG "%s: pcan_fifo_reset() %d %p %p %d\n", DEVICE_NAME, anchor->nStored, anchor->r, anchor->w, anchor->bGetClaimed);

  return 0;
}

int pcan_fifo_init(register FIFO_MANAGER *anchor, void *bufferBegin, void *bufferEnd, int nCount, u16 wCopySize)
{
	anchor->wCopySize   = wCopySize;
	anchor->wStepSize   = (bufferBegin == bufferEnd) ? 0 : ((bufferEnd - bufferBegin) / (nCount - 1));
	anchor->nCount      = nCount;
	anchor->bufferBegin = bufferBegin;
	anchor->bufferEnd   = bufferEnd;
	anchor->flags       = 0;
	
	// check for fatal program errors
	if ((anchor->wStepSize < anchor->wCopySize) || (anchor->bufferBegin > anchor->bufferEnd) || (nCount <= 1))
	  return -EINVAL;
	
	spin_lock_init(&anchor->lock);

  return pcan_fifo_reset(anchor);
}

//----------------------------------------------------------------------------
// the functiony claim and ..put or ..get are always a timingly narrow related
// pair. Never there must be a .._claim_.. without a .._put or .._get! 
int pcan_fifo_claim_for_put(register FIFO_MANAGER *anchor, void **pvPutData)
{
  int err = 0;
  
  if (anchor->bPutClaimed)
    err = EAGAIN;
  else
  {
    spin_lock_irqsave(&anchor->lock, anchor->flags);
  
    if (anchor->nStored < anchor->nCount)
    {
      anchor->bPutClaimed = 1;
      *pvPutData = anchor->w;
    }
    else
    {
	    spin_unlock_irqrestore(&anchor->lock, anchor->flags);  
      *pvPutData = NULL;
      err = -ENOSPC;
    }
  }
  
  return err;
}

int pcan_fifo_put(register FIFO_MANAGER *anchor)
{
  // DPRINTK(KERN_DEBUG "%s: pcan_fifo_put() %d %p %p %d\n", DEVICE_NAME, anchor->nStored, anchor->r, anchor->w, anchor->bPutClaimed);

  if (!anchor->bPutClaimed)
    return -EDEADLOCK;

  anchor->nStored++;
  anchor->dwTotal++;
  if (anchor->w < anchor->bufferEnd)
    anchor->w += anchor->wStepSize;   // increment to next
  else
    anchor->w = anchor->bufferBegin;  // start from begin
  
	anchor->bPutClaimed = 0; 
	spin_unlock_irqrestore(&anchor->lock, anchor->flags); 
	
  return 0;
}

int pcan_fifo_put_reject(register FIFO_MANAGER *anchor)
{
  // DPRINTK(KERN_DEBUG "%s: pcan_fifo_put_reject() %d %p %p %d\n", DEVICE_NAME, anchor->nStored, anchor->r, anchor->w, anchor->bPutClaimed);

  if (!anchor->bPutClaimed)
    return -EDEADLOCK;

	anchor->bPutClaimed = 0; 
	spin_unlock_irqrestore(&anchor->lock, anchor->flags); 
	
  return 0;
}

//----------------------------------------------------------------------------
// the functiony claim and ..put or ..get are always a timingly narrow related
// pair. There should never be a .._claim_.. without a .._put or .._get! 
int pcan_fifo_claim_for_get(register FIFO_MANAGER *anchor, void **pvGetData)
{
  int err = 0;
  
  if (anchor->bGetClaimed)
    err = EAGAIN;
  else
  {
    spin_lock_irqsave(&anchor->lock, anchor->flags);
  
    if (anchor->nStored > 0)
    {
       anchor->bGetClaimed = 1;
       *pvGetData = anchor->r;
    }
    else
    {
	    spin_unlock_irqrestore(&anchor->lock, anchor->flags);
      *pvGetData = NULL;
	    err = -ENODATA;
    }
  }
  
  return err;
}

int pcan_fifo_get(register FIFO_MANAGER *anchor)
{
  // DPRINTK(KERN_DEBUG "%s: pcan_fifo_get() %d %p %p %d\n", DEVICE_NAME, anchor->nStored, anchor->r, anchor->w, anchor->bGetClaimed);

  if (!anchor->bGetClaimed)
    return -EDEADLOCK;
    
  anchor->nStored--;
  if (anchor->r < anchor->bufferEnd)
    anchor->r += anchor->wStepSize;  // increment to next   
  else
    anchor->r = anchor->bufferBegin; // start from begin
  
	anchor->bGetClaimed = 0; 
	spin_unlock_irqrestore(&anchor->lock, anchor->flags);
	
  return 0;
}

int pcan_fifo_get_reject(register FIFO_MANAGER *anchor)
{
  // DPRINTK(KERN_DEBUG "%s: pcan_fifo_get_reject() %d %p %p %d\n", DEVICE_NAME, anchor->nStored, anchor->r, anchor->w, anchor->bGetClaimed);

  if (!anchor->bGetClaimed)
    return -EDEADLOCK;
    
	anchor->bGetClaimed = 0; 
	spin_unlock_irqrestore(&anchor->lock, anchor->flags);
	
  return 0;
}

//----------------------------------------------------------------------------
// returns the current count of elements in fifo
int pcan_fifo_status(FIFO_MANAGER *anchor)
{
  return anchor->nStored;
}

//----------------------------------------------------------------------------
// returns 0 if the fifo is full
int pcan_fifo_near_full(FIFO_MANAGER *anchor)
{
  return (anchor->nStored < (anchor->nCount - 1));
} 

//----------------------------------------------------------------------------
// returns 0 if the fifo is empty
int pcan_fifo_empty(FIFO_MANAGER *anchor)
{
  return anchor->nStored;
}


